// Copyright (C) 2024 EA group inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <turbo/strings/internal/str_format/parser.h>

#include <string.h>
#include <algorithm>
#include <initializer_list>
#include <string>
#include <utility>

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <turbo/base/config.h>
#include <turbo/base/macros.h>
#include <turbo/strings/internal/str_format/constexpr_parser.h>
#include <turbo/strings/internal/str_format/extension.h>
#include <turbo/strings/string_view.h>

namespace turbo {
    TURBO_NAMESPACE_BEGIN
    namespace str_format_internal {

        namespace {

            using testing::Pair;

            TEST(LengthModTest, Names) {
                struct Expectation {
                    int line;
                    LengthMod mod;
                    const char *name;
                };
                const Expectation kExpect[] = {
                        {__LINE__, LengthMod::none, ""},
                        {__LINE__, LengthMod::h,    "h"},
                        {__LINE__, LengthMod::hh,   "hh"},
                        {__LINE__, LengthMod::l,    "l"},
                        {__LINE__, LengthMod::ll,   "ll"},
                        {__LINE__, LengthMod::L,    "L"},
                        {__LINE__, LengthMod::j,    "j"},
                        {__LINE__, LengthMod::z,    "z"},
                        {__LINE__, LengthMod::t,    "t"},
                        {__LINE__, LengthMod::q,    "q"},
                };
                EXPECT_EQ(TURBO_ARRAYSIZE(kExpect), 10);
                for (auto e: kExpect) {
                    SCOPED_TRACE(e.line);
                    EXPECT_EQ(e.name, LengthModToString(e.mod));
                }
            }

            TEST(ConversionCharTest, Names) {
                struct Expectation {
                    FormatConversionChar id;
                    char name;
                };
                // clang-format off
                const Expectation kExpect[] = {
#define X(c) {FormatConversionCharInternal::c, #c[0]}
                        X(c), X(s),                                      // text
                        X(d), X(i), X(o), X(u), X(x), X(X),              // int
                        X(f), X(F), X(e), X(E), X(g), X(G), X(a), X(A),  // float
                        X(n), X(p),                                      // misc
#undef X
                        {FormatConversionCharInternal::kNone, '\0'},
                };
                // clang-format on
                for (auto e: kExpect) {
                    SCOPED_TRACE(e.name);
                    FormatConversionChar v = e.id;
                    EXPECT_EQ(e.name, FormatConversionCharToChar(v));
                }
            }

            class ConsumeUnboundConversionTest : public ::testing::Test {
            public:
                std::pair<std::string_view, std::string_view> Consume(std::string_view src) {
                    int next = 0;
                    o = UnboundConversion();  // refresh
                    const char *p = ConsumeUnboundConversion(
                            src.data(), src.data() + src.size(), &o, &next);
                    if (!p) return {{}, src};
                    return {std::string_view(src.data(), p - src.data()),
                            std::string_view(p, src.data() + src.size() - p)};
                }

                bool Run(const char *fmt, bool force_positional = false) {
                    int next = force_positional ? -1 : 0;
                    o = UnboundConversion();  // refresh
                    return ConsumeUnboundConversion(fmt, fmt + strlen(fmt), &o, &next) ==
                           fmt + strlen(fmt);
                }

                UnboundConversion o;
            };

            TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) {
                struct Expectation {
                    int line;
                    std::string_view src;
                    std::string_view out;
                    std::string_view src_post;
                };
                const Expectation kExpect[] = {
                        {__LINE__, "",             "",             ""},
                        {__LINE__, "b",            "",             "b"},  // 'b' is invalid
                        {__LINE__, "ba",           "",             "ba"},  // 'b' is invalid
                        {__LINE__, "l",            "",             "l"},  // just length mod isn't okay
                        {__LINE__, "d",            "d",            ""},  // basic
                        {__LINE__, "v",            "v",            ""},  // basic
                        {__LINE__, "d ",           "d",            " "},  // leave suffix
                        {__LINE__, "dd",           "d",            "d"},  // don't be greedy
                        {__LINE__, "d9",           "d",            "9"},  // leave non-space suffix
                        {__LINE__, "dzz",          "d",            "zz"},  // length mod as suffix
                        {__LINE__, "3v",           "",             "3v"},  // 'v' cannot have modifiers
                        {__LINE__, "hv",           "",             "hv"},  // 'v' cannot have modifiers
                        {__LINE__, "1$v",          "1$v",          ""},  // 'v' can have use posix syntax
                        {__LINE__, "1$*2$d",       "1$*2$d",       ""},  // arg indexing and * allowed.
                        {__LINE__, "0-14.3hhd",    "0-14.3hhd",    ""},  // precision, width
                        {__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""},  // flags
                };
                for (const auto &e: kExpect) {
                    SCOPED_TRACE(e.line);
                    EXPECT_THAT(Consume(e.src), Pair(e.out, e.src_post));
                }
            }

            TEST_F(ConsumeUnboundConversionTest, BasicConversion) {
                EXPECT_FALSE(Run(""));
                EXPECT_FALSE(Run("z"));

                EXPECT_FALSE(Run("dd"));  // no excess allowed

                EXPECT_TRUE(Run("d"));
                EXPECT_EQ('d', FormatConversionCharToChar(o.conv));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_LT(o.width.value(), 0);
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_LT(o.precision.value(), 0);
                EXPECT_EQ(1, o.arg_position);
            }

            TEST_F(ConsumeUnboundConversionTest, ArgPosition) {
                EXPECT_TRUE(Run("d"));
                EXPECT_EQ(1, o.arg_position);
                EXPECT_TRUE(Run("3$d"));
                EXPECT_EQ(3, o.arg_position);
                EXPECT_TRUE(Run("1$d"));
                EXPECT_EQ(1, o.arg_position);
                EXPECT_TRUE(Run("1$d", true));
                EXPECT_EQ(1, o.arg_position);
                EXPECT_TRUE(Run("123$d"));
                EXPECT_EQ(123, o.arg_position);
                EXPECT_TRUE(Run("123$d", true));
                EXPECT_EQ(123, o.arg_position);
                EXPECT_TRUE(Run("10$d"));
                EXPECT_EQ(10, o.arg_position);
                EXPECT_TRUE(Run("10$d", true));
                EXPECT_EQ(10, o.arg_position);

                // Position can't be zero.
                EXPECT_FALSE(Run("0$d"));
                EXPECT_FALSE(Run("0$d", true));
                EXPECT_FALSE(Run("1$*0$d"));
                EXPECT_FALSE(Run("1$.*0$d"));

                // Position can't start with a zero digit at all. That is not a 'decimal'.
                EXPECT_FALSE(Run("01$p"));
                EXPECT_FALSE(Run("01$p", true));
                EXPECT_FALSE(Run("1$*01$p"));
                EXPECT_FALSE(Run("1$.*01$p"));
            }

            TEST_F(ConsumeUnboundConversionTest, WidthAndPrecision) {
                EXPECT_TRUE(Run("14d"));
                EXPECT_EQ('d', FormatConversionCharToChar(o.conv));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_EQ(14, o.width.value());
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_LT(o.precision.value(), 0);

                EXPECT_TRUE(Run("14.d"));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_EQ(14, o.width.value());
                EXPECT_EQ(0, o.precision.value());

                EXPECT_TRUE(Run(".d"));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_LT(o.width.value(), 0);
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_EQ(0, o.precision.value());

                EXPECT_TRUE(Run(".5d"));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_LT(o.width.value(), 0);
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_EQ(5, o.precision.value());

                EXPECT_TRUE(Run(".0d"));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_LT(o.width.value(), 0);
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_EQ(0, o.precision.value());

                EXPECT_TRUE(Run("14.5d"));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_EQ(14, o.width.value());
                EXPECT_EQ(5, o.precision.value());

                EXPECT_TRUE(Run("*.*d"));
                EXPECT_TRUE(o.width.is_from_arg());
                EXPECT_EQ(1, o.width.get_from_arg());
                EXPECT_TRUE(o.precision.is_from_arg());
                EXPECT_EQ(2, o.precision.get_from_arg());
                EXPECT_EQ(3, o.arg_position);

                EXPECT_TRUE(Run("*d"));
                EXPECT_TRUE(o.width.is_from_arg());
                EXPECT_EQ(1, o.width.get_from_arg());
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_LT(o.precision.value(), 0);
                EXPECT_EQ(2, o.arg_position);

                EXPECT_TRUE(Run(".*d"));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_LT(o.width.value(), 0);
                EXPECT_TRUE(o.precision.is_from_arg());
                EXPECT_EQ(1, o.precision.get_from_arg());
                EXPECT_EQ(2, o.arg_position);

                // mixed implicit and explicit: didn't specify arg position.
                EXPECT_FALSE(Run("*23$.*34$d"));

                EXPECT_TRUE(Run("12$*23$.*34$d"));
                EXPECT_EQ(12, o.arg_position);
                EXPECT_TRUE(o.width.is_from_arg());
                EXPECT_EQ(23, o.width.get_from_arg());
                EXPECT_TRUE(o.precision.is_from_arg());
                EXPECT_EQ(34, o.precision.get_from_arg());

                EXPECT_TRUE(Run("2$*5$.*9$d"));
                EXPECT_EQ(2, o.arg_position);
                EXPECT_TRUE(o.width.is_from_arg());
                EXPECT_EQ(5, o.width.get_from_arg());
                EXPECT_TRUE(o.precision.is_from_arg());
                EXPECT_EQ(9, o.precision.get_from_arg());

                EXPECT_FALSE(Run(".*0$d")) << "no arg 0";

                // Large values
                EXPECT_TRUE(Run("999999999.999999999d"));
                EXPECT_FALSE(o.width.is_from_arg());
                EXPECT_EQ(999999999, o.width.value());
                EXPECT_FALSE(o.precision.is_from_arg());
                EXPECT_EQ(999999999, o.precision.value());

                EXPECT_FALSE(Run("1000000000.999999999d"));
                EXPECT_FALSE(Run("999999999.1000000000d"));
                EXPECT_FALSE(Run("9999999999d"));
                EXPECT_FALSE(Run(".9999999999d"));
            }

            TEST_F(ConsumeUnboundConversionTest, Flags) {
                static const char kAllFlags[] = "-+ #0";
                static const int kNumFlags = TURBO_ARRAYSIZE(kAllFlags) - 1;
                for (int rev = 0; rev < 2; ++rev) {
                    for (int i = 0; i < 1 << kNumFlags; ++i) {
                        std::string fmt;
                        for (int k = 0; k < kNumFlags; ++k)
                            if ((i >> k) & 1) fmt += kAllFlags[k];
                        // flag order shouldn't matter
                        if (rev == 1) {
                            std::reverse(fmt.begin(), fmt.end());
                        }
                        fmt += 'd';
                        SCOPED_TRACE(fmt);
                        EXPECT_TRUE(Run(fmt.c_str()));
                        EXPECT_EQ(fmt.find('-') == std::string::npos,
                                  !FlagsContains(o.flags, Flags::kLeft));
                        EXPECT_EQ(fmt.find('+') == std::string::npos,
                                  !FlagsContains(o.flags, Flags::kShowPos));
                        EXPECT_EQ(fmt.find(' ') == std::string::npos,
                                  !FlagsContains(o.flags, Flags::kSignCol));
                        EXPECT_EQ(fmt.find('#') == std::string::npos,
                                  !FlagsContains(o.flags, Flags::kAlt));
                        EXPECT_EQ(fmt.find('0') == std::string::npos,
                                  !FlagsContains(o.flags, Flags::kZero));
                    }
                }
            }

            TEST_F(ConsumeUnboundConversionTest, BasicFlag) {
                // Flag is on
                for (const char *fmt: {"d", "llx", "G", "1$X"}) {
                    SCOPED_TRACE(fmt);
                    EXPECT_TRUE(Run(fmt));
                    EXPECT_EQ(o.flags, Flags::kBasic);
                }

                // Flag is off
                for (const char *fmt: {"3d", ".llx", "-G", "1$#X", "lc"}) {
                    SCOPED_TRACE(fmt);
                    EXPECT_TRUE(Run(fmt));
                    EXPECT_NE(o.flags, Flags::kBasic);
                }
            }

            TEST_F(ConsumeUnboundConversionTest, LengthMod) {
                EXPECT_TRUE(Run("d"));
                EXPECT_EQ(LengthMod::none, o.length_mod);
                EXPECT_TRUE(Run("hd"));
                EXPECT_EQ(LengthMod::h, o.length_mod);
                EXPECT_TRUE(Run("hhd"));
                EXPECT_EQ(LengthMod::hh, o.length_mod);
                EXPECT_TRUE(Run("ld"));
                EXPECT_EQ(LengthMod::l, o.length_mod);
                EXPECT_TRUE(Run("lld"));
                EXPECT_EQ(LengthMod::ll, o.length_mod);
                EXPECT_TRUE(Run("Lf"));
                EXPECT_EQ(LengthMod::L, o.length_mod);
                EXPECT_TRUE(Run("qf"));
                EXPECT_EQ(LengthMod::q, o.length_mod);
                EXPECT_TRUE(Run("jd"));
                EXPECT_EQ(LengthMod::j, o.length_mod);
                EXPECT_TRUE(Run("zd"));
                EXPECT_EQ(LengthMod::z, o.length_mod);
                EXPECT_TRUE(Run("td"));
                EXPECT_EQ(LengthMod::t, o.length_mod);
            }

            struct SummarizeConsumer {
                std::string *out;

                explicit SummarizeConsumer(std::string *out) : out(out) {}

                bool Append(std::string_view s) {
                    *out += "[" + std::string(s) + "]";
                    return true;
                }

                bool ConvertOne(const UnboundConversion &conv, std::string_view s) {
                    *out += "{";
                    *out += std::string(s);
                    *out += ":";
                    *out += std::to_string(conv.arg_position) + "$";
                    if (conv.width.is_from_arg()) {
                        *out += std::to_string(conv.width.get_from_arg()) + "$*";
                    }
                    if (conv.precision.is_from_arg()) {
                        *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*";
                    }
                    *out += FormatConversionCharToChar(conv.conv);
                    *out += "}";
                    return true;
                }
            };

            std::string SummarizeParsedFormat(const ParsedFormatBase &pc) {
                std::string out;
                if (!pc.ProcessFormat(SummarizeConsumer(&out))) out += "!";
                return out;
            }

            class ParsedFormatTest : public testing::Test {
            };

            TEST_F(ParsedFormatTest, ValueSemantics) {
                ParsedFormatBase p1({}, true, {});  // empty format
                EXPECT_EQ("", SummarizeParsedFormat(p1));

                ParsedFormatBase p2 = p1;  // copy construct (empty)
                EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2));

                p1 = ParsedFormatBase("hello%s", true,
                                      {FormatConversionCharSetInternal::s});  // move assign
                EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p1));

                ParsedFormatBase p3 = p1;  // copy construct (nonempty)
                EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p3));

                using std::swap;
                swap(p1, p2);
                EXPECT_EQ("", SummarizeParsedFormat(p1));
                EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p2));
                swap(p1, p2);  // undo

                p2 = p1;  // copy assign
                EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2));
            }

            struct ExpectParse {
                const char *in;
                std::initializer_list<FormatConversionCharSet> conv_set;
                const char *out;
            };

            TEST_F(ParsedFormatTest, Parsing) {
                // Parse should be equivalent to that obtained by ConversionParseIterator.
                // No need to retest the parsing edge cases here.
                const ExpectParse kExpect[] = {
                        {"",       {},                                   ""},
                        {"ab",     {},                                   "[ab]"},
                        {"a%d",    {FormatConversionCharSetInternal::d}, "[a]{d:1$d}"},
                        {"a%+d",   {FormatConversionCharSetInternal::d}, "[a]{+d:1$d}"},
                        {"a% d",   {FormatConversionCharSetInternal::d}, "[a]{ d:1$d}"},
                        {"a%b %d", {},                                   "[a]!"},  // stop after error
                };
                for (const auto &e: kExpect) {
                    SCOPED_TRACE(e.in);
                    EXPECT_EQ(e.out,
                              SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set)));
                }
            }

            TEST_F(ParsedFormatTest, ParsingFlagOrder) {
                const ExpectParse kExpect[] = {
                        {"a%+ 0d",    {FormatConversionCharSetInternal::d}, "[a]{+ 0d:1$d}"},
                        {"a%+0 d",    {FormatConversionCharSetInternal::d}, "[a]{+0 d:1$d}"},
                        {"a%0+ d",    {FormatConversionCharSetInternal::d}, "[a]{0+ d:1$d}"},
                        {"a% +0d",    {FormatConversionCharSetInternal::d}, "[a]{ +0d:1$d}"},
                        {"a%0 +d",    {FormatConversionCharSetInternal::d}, "[a]{0 +d:1$d}"},
                        {"a% 0+d",    {FormatConversionCharSetInternal::d}, "[a]{ 0+d:1$d}"},
                        {"a%+   0+d", {FormatConversionCharSetInternal::d}, "[a]{+   0+d:1$d}"},
                };
                for (const auto &e: kExpect) {
                    SCOPED_TRACE(e.in);
                    EXPECT_EQ(e.out,
                              SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set)));
                }
            }

        }  // namespace
    }  // namespace str_format_internal
    TURBO_NAMESPACE_END
}  // namespace turbo
